package com.eunion.core.common.service;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.slf4j.LoggerFactory;

import com.eunion.core.common.excel.ExcelColumnRestriction;
import com.eunion.core.common.excel.ExcelColumnRestriction.Type;
import com.eunion.core.common.excel.ExcelImportWarning;
import com.eunion.core.common.excel.ProgressCallback;
import com.eunion.core.common.excel.exception.AbortImportException;
import com.eunion.core.common.excel.exception.FormulaErrorException;
import com.eunion.core.common.jpa.FlushControl;
import com.eunion.core.DateUtil;
import com.eunion.core.common.domain.AbstractExcelReport;
import com.eunion.core.common.domain.ExcelUploadRecord;
import com.eunion.core.common.domain.enumeration.RecordEnableFlag;
import com.eunion.core.common.domain.enumeration.UploadDepartment;

/**
 * 通用excel导入服务接口
 *
 * @author hallwong
 */
public interface ExcelImportService<T extends AbstractExcelReport> {

    Pattern YEAR_MONTH_PATTERN = Pattern.compile("(\\d{4})年(\\d{1,2})月");

    Pattern MONTH_DAY_PATTERN = Pattern.compile("(\\d{1,2})月(\\d{1,2})日");

    /**
     * 通用excel导入服务接口
     *
     * @param uploadRecord
     *            相关联的上传记录
     * @param in
     *            excel文件输入流
     * @param callback
     *            过程回调函数
     * @return 报表导入结果
     * @throws IOException
     *             解析报表时可能抛出
     * @throws AbortImportException
     *             报表数据有误时抛出
     * @throws FormulaErrorException
     */
    default Collection<ExcelImportWarning> importData(String dept, ExcelUploadRecord uploadRecord, InputStream in,
        ProgressCallback callback) throws IOException, AbortImportException, FormulaErrorException {
        LoggerFactory.getLogger(getClass()).info("start to import...");
        callback.callback(10);
        Workbook workbook = openFile(in);
        try {
            callback.callback(40);
            List<ExcelImportWarning> warnings = new ArrayList<>();
            List<T> dataList = parse(uploadRecord, workbook, warnings);
            validateWarnings(warnings);
            callback.callback(50);
            UploadDepartment uploadDepartment;
            if (dept.contains("电力")) {
                uploadDepartment = UploadDepartment.DL;
            } else if (dept.contains("监控中心")) {
                uploadDepartment = UploadDepartment.DL; // 上传部门，1为监控中心
            } else {
                uploadDepartment = UploadDepartment.NR;
            }
            dataList.forEach(e -> e.setUploadDepartment(uploadDepartment));
            int size = dataList.size();
            save(dataList, (em, index) -> {
                if (index % 30 == 0) {
                    callback.callback(index * (100 - 50) / size + 50);
                }
            });
            callback.callback(100);
            return warnings;
        } finally {
            try {
                ((InputStream) workbook).close();
            } catch (Exception e) {
                LoggerFactory.getLogger(getClass()).error("close excel error", e);
            }
        }
    }

    /**
     * 打开excel文件，这个操作比较耗时
     *
     * @param in
     *            文件的二进制流
     * @param callback
     *            完成度回调函数
     * @return
     * @throws IOException
     *             打开文件时可能出现的异常
     */
    default Workbook openFile(InputStream in) throws IOException {
        Workbook workbook;
        try {
            workbook = WorkbookFactory.create(in);
        } catch (InvalidFormatException e) {
            throw new IOException("格式错误", e);
        }
        return workbook;
    }

    /**
     * 将excel中的数据解析成数据
     *
     * @param uploadRecord
     * @param workbook
     * @return
     * @throws AbortImportException
     * @throws FormulaErrorException
     */
    List<T> parse(ExcelUploadRecord uploadRecord, Workbook workbook, List<ExcelImportWarning> warnings)
        throws IOException, AbortImportException, FormulaErrorException;

    /**
     * 将解析后的数据持久化到持久层
     *
     * @param dataList
     *            解析后的数据
     * @throws IOException
     */
    void save(List<T> dataList, FlushControl flushControl);

    /**
     * 设置导入数据的上传人信息
     *
     * @param report
     */
    default void wrapReport(AbstractExcelReport report) {
        report.setEnable(RecordEnableFlag.ENABLE);
        // report.setUserId(userId);
        // report.setUserName(userName);
        // report.setUploadDepartment(uploadDepartment);
    }

    default void validateWarnings(List<ExcelImportWarning> warnings) throws AbortImportException {
        if (warnings.isEmpty()) {
            return;
        }
        ExcelImportWarning lastWarning = warnings.get(warnings.size() - 1);
        if (lastWarning.getOperation() == ExcelImportWarning.Operation.ABORT) {
            throw AbortImportException.throwOut(warnings);
        }
    }

    default Object readValue(Row row, ExcelColumnRestriction meta) {
        return readValue(row, meta, true);
    }

    default Object readValue(Row row, ExcelColumnRestriction meta, boolean trim) {
        Cell cell = row.getCell(meta.getIndex());
        if (cell == null) {
            return null;
        }
        if (meta.getType() == Type.Number) {
            if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
                cell.setCellType(Cell.CELL_TYPE_NUMERIC);
            } else if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
                cell.setCellType(Cell.CELL_TYPE_NUMERIC);
                return cell.getNumericCellValue();
                // return null;
            } else if (meta.isDatetimeType()) {
                // 如果是日期时间格式，则需要转换一下。
                double timeValue = cell.getNumericCellValue();
                // 调用POI提供的根据double数字得到Date的方法，更为准确。
                Date tempDate = org.apache.poi.ss.usermodel.DateUtil.getJavaDate(timeValue);
                // 如果指定了日期格式，则格式化为字符串。
                if (StringUtils.isNotEmpty(meta.getDateFormat())) {
                    return DateUtil.dateToString(tempDate, meta.getDateFormat());
                }
                // 否则，直接返回日期格式。
                return tempDate;
            }

            return cell.getNumericCellValue();
        } else if (meta.getType() == Type.String) {
            if (cell.getCellType() != Cell.CELL_TYPE_STRING) {
                cell.setCellType(Cell.CELL_TYPE_STRING);
            }
            String originValue = cell.getStringCellValue();
            if (!trim || originValue == null) {
                return originValue;
            } else if (originValue.trim().isEmpty()) {
                return null;
            }
            return originValue.trim();
        }
        return null;
    }

}
